import logging
import re
from datetime import datetime
from random import randint

from django.contrib.auth import get_user_model, authenticate
from django_redis import get_redis_connection
from ninja import Router
from ninja.responses import Response
from ninja_extra import api_controller, route
from ninja_extra import status
from ninja_jwt import schema
from ninja_jwt.controller import TokenObtainSlidingController
from ninja_jwt.tokens import SlidingToken

from carts.utils import merge_cookie_redis
from celery_tasks.sms.tasks import send_sms_code
from meiduo_mall.base.schemas import Message
from users.schemas import UserTokenOutSchema, LoginSchema
from . import constants

api = Router(tags=['手机验证码'])
User = get_user_model()
logger = logging.getLogger('django')


@api.get('/verify/{mobile}', tags=['注册校验'], summary='获取手机验证码')
# 'http://127.0.0.1:8000/sms/verify/17784177787'
def verify(request, mobile: int):
    # description='获取手机验证码'与下面的注释方式效果一样
    """获取手机验证码"""
    if re.match(r'1[3-9]\d{9}', str(mobile)):
        # 1.创建redis连接对象
        redis_conn = get_redis_connection('verify_code')
        # 2.获取发送验证码标记
        send_flag = redis_conn.get(f'send_flag_{mobile}')  # 取到标记或None
        # 3.判断是否发送过验证码
        if send_flag:  # 能取到说明已发送
            return Response({'message': '已发送过验证码,60秒内请勿再次发送'}, status=status.HTTP_400_BAD_REQUEST)

        # 4.生成验证码
        sms_code = "%06d" % randint(0, 999999)
        logger.info(sms_code)

        # 5.验证码存到redis数据库
        # 补充:创建redis管道:把多次连接redis的操作装入管道中,将来一次性执行,减少redis连接操作
        pl = redis_conn.pipeline()
        pl.setex(f'sms_{mobile}', constants.SMS_CODE_REDIS_EXPIRES, sms_code)
        # 6.存储一个已发送的标记,有效期60秒
        # redis_conn.setex(f'send_flag_{mobile}', constants.SEND_SMS_CODE_INTERVAL, 1)
        pl.setex(f'send_flag_{mobile}', constants.SEND_SMS_CODE_INTERVAL, 1)
        # 补充:把操作放入管道中后再统一一起执行
        pl.execute()

        # 7.用短信服务发送
        # CCP().send_template_sms(self,to=手机号,datas=[验证码,5(过期时间,分钟),temp_id=消息模板ID])
        # CCP().send_template_sms(mobile, [sms_code, constants.SMS_CODE_REDIS_EXPIRES // 60], 1)

        # 9.异步方式发送验证码,必须通过delay来调用异步任务
        send_sms_code.delay(mobile, sms_code)

        # 8.响应
        return Response({'message': '验证码已发送'}, status=status.HTTP_200_OK)

    return Response({'message': '手机号码格式不正确'}, status=status.HTTP_400_BAD_REQUEST)


# TODO：尝试更换成AccessToken
@api_controller("/auth", tags=["登录认证"])
class UserTokenController(TokenObtainSlidingController):
    """ninja-jwt滑动令牌：安全性较低"""

    @route.post("/login", response={200: UserTokenOutSchema, 400: Message}, summary="登录", url_name="login")
    def obtain_token(self, data: LoginSchema):
        """
        获取用户对象并生成令牌

        Args:
            auth (LoginSchema): 用户登录信息

        Returns:
            UserTokenOutSchema: 包含用户对象、令牌和过期时间的输出对象

            :param data: 用户登录信息
        """
        # 一。获取用户对象  --用户名登录方式
        # user_obj = authenticate(username=data.username, password=data.password)

        # 二。手动用户名和手机号登录     authenticate会调用check_password，自己手写则需自己用check_password进行密码验证
        # user = User.objects.filter(Q(username=data.username) | Q(mobile=data.username)).first()
        # user_obj = user if user.check_password(raw_password=data.password) else None

        # 三。重写认证类方法实现多账号登录  settings.py设置不成功，手动配置
        # 失败原因：设置里面配置认证路径须用列表，
        # AUTHENTICATION_BACKENDS = ['meiduo_mall.utils.customAuth.UsernameMobileAuthBackend', ]
        user_obj = authenticate(self.context.request, username=data.username, password=data.password)
        # 判断用户是否存在
        if not user_obj:
            return 400, {'msg': "用户名或密码错误"}
        # 生成令牌
        token = SlidingToken.for_user(user_obj)
        data = UserTokenOutSchema(
            user=user_obj,
            token=str(token),
            token_exp_date=datetime.fromtimestamp(token["exp"])
        )
        response = Response(status=200, data=data)
        merge_cookie_redis(self.context.request, user_obj, response)
        return response

    @route.post(
        "/api-token-refresh",
        response=schema.TokenRefreshSlidingSerializer,
        summary="刷新token",
        url_name="refresh",
    )
    def refresh_token(self, refresh_token: schema.TokenRefreshSlidingSchema):
        """
        刷新令牌

        参数:
            refresh_token (schema.TokenRefreshSlidingSchema): 用于刷新令牌的参数对象

        返回:
            refresh (schema.TokenRefreshSlidingSchema): 刷新后的令牌对象
        """
        refresh = schema.TokenRefreshSlidingSerializer(**refresh_token.dict())
        return refresh
